package com.zhuhjay.common.core.security.filter;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.zhuhjay.common.base.enums.HttpCode;
import com.zhuhjay.common.base.util.JacksonUtils;
import com.zhuhjay.common.core.util.JwtUtils;
import com.zhuhjay.common.redis.util.RedisUtils;
import com.zhuhjay.common.redis.util.RedissonUtils;
import com.zhuhjay.common.security.LoginUserDetail;
import com.zhuhjay.common.security.context.AuthenticationExceptionContext;
import com.zhuhjay.common.security.context.LoginUserContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

import static com.zhuhjay.common.base.constant.CacheConstant.TOKEN_PREFIX;
import static com.zhuhjay.common.base.constant.CacheConstant.USER_UNIQUE_ID_PREFIX;
import static com.zhuhjay.common.base.constant.SecurityConstant.*;
import static com.zhuhjay.common.base.constant.TimeConstant.DATE_TIME_FORMAT;

/**
 * 此过滤器会自动解析HTTP请求中头部名字为Authentication，且以Basic开头的头信息。
 * @author ZhuHJay
 * @date 2023/2/25 0:39
 */
@Slf4j
public class BasicTokenVerifyFilter extends BasicAuthenticationFilter {

    public BasicTokenVerifyFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 只有登录成功后才会执行到该Filter过滤器中, 进行token有效校验
        String authorization = request.getHeader(AUTH_HEADER_KEY);
        if (StrUtil.isNotBlank(authorization) && authorization.startsWith(AUTH_REQ_TITLE)) {
            // 判断token是否有效(redis中失效的即token失效)
            String token = JwtUtils.exportToken(authorization);
            String uid = RedisUtils.getCache(TOKEN_PREFIX + token);
            if (StrUtil.isBlank(uid)) {
                // token失效
                AuthenticationExceptionContext.setCode(HttpCode.LOGIN_FAILURE);
            } else {
                String infoStr = RedisUtils.getCache(USER_UNIQUE_ID_PREFIX + uid);
                if (StrUtil.isBlank(infoStr)) {
                    // 用户被迫下线
                    // 清理token缓存
                    RedisUtils.deleteCache(TOKEN_PREFIX + token);
                    AuthenticationExceptionContext.setCode(HttpCode.FORCED_TO_LOGOFF);
                } else {
                    // 根据token中的uid(value可以等价于token解析后的uid)获取到用户信息
                    LoginUserDetail loginUserDetail = JacksonUtils.readValue(infoStr, LoginUserDetail.class);

                    // 放到SpringSecurity上下文对象中(不将权限信息交给其进行管理)
                    SecurityContextHolder.getContext().setAuthentication(
                            new UsernamePasswordAuthenticationToken(
                                    loginUserDetail, null,
                                    // 直接给一个默认的权限, 但是不会被用到
                                    List.of(new SimpleGrantedAuthority("ROLE_USER"))
                            )
                    );

                    // 将用户信息放到线程上下文中
                    LoginUserContext.setUserId(loginUserDetail.getId());
                    LoginUserContext.setUsername(loginUserDetail.getUsername());
                    LoginUserContext.setUid(String.valueOf(loginUserDetail.getUid()));

                    // 续签
                    renew(token, loginUserDetail);
                }
            }
        } else {
            // token为空
            AuthenticationExceptionContext.setCode(HttpCode.LIMITED_ACCESS);
        }

        chain.doFilter(request, response);

        // 清空线程上下文
        LoginUserContext.clear();
        AuthenticationExceptionContext.clear();
    }

    /** 续签 **/
    private void renew(String token, LoginUserDetail loginUserDetail) {
        // 判断是否需要续签
        if (!JwtUtils.isRenew(loginUserDetail)) {
            return;
        }
        String tokenKey = TOKEN_PREFIX + token;
        String uidKey = USER_UNIQUE_ID_PREFIX + loginUserDetail.getUid();
        // 使用分布式锁防止并发问题
        RedissonUtils.withFastFailTryLock(uidKey, () -> {
            // 获取最新的用户信息
            LoginUserDetail newDetail = JacksonUtils.readValue(
                    RedisUtils.getCache(uidKey),
                    LoginUserDetail.class);
            // 再次判断是否需要续签, 防止并发问题
            if (!JwtUtils.isRenew(newDetail)) {
                return;
            }
            // 偷偷记录个日志
            log.info("用户[{}]续签, 开始t: {}, 结束t: {}, 当前t: {}",
                    newDetail.getUsername(),
                    LocalDateTimeUtil.format(newDetail.getLoginTime(), DATE_TIME_FORMAT),
                    LocalDateTimeUtil.format(newDetail.getExpireTime(), DATE_TIME_FORMAT),
                    LocalDateTimeUtil.format(LocalDateTimeUtil.now(), DATE_TIME_FORMAT)
            );
            // 刷新过期时间
            JwtUtils.refreshExpireTime(newDetail);
            // 修改token的过期时间
            RedisUtils.setKeyExpire(tokenKey, JWT_EXPIRATION_TIME, JWT_EXPIRATION_TIME_UNIT);
            // 修改uid的过期时间
            RedisUtils.setCacheAndExpire(uidKey, JacksonUtils.writeValueAsString(newDetail), JWT_EXPIRATION_TIME, JWT_EXPIRATION_TIME_UNIT);
        });
    }
}
